package com.cycrix.tcp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import android.os.Handler;
import android.os.Message;

public class TcpConnection implements Runnable {

	public static final int BUFFER_SIZE = 2048 * 1024;
	
	public static final int DID_RECEIVE = 0;
	public static final int DID_CLOSE = 1;
	public static final int DID_ERROR = 2;
	
	public static final int ENUM_COMMAND = 0;
	public static final int ENUM_TEXT = 1;
	public static final int ENUM_DUMMY = 2;
	public static final int ENUM_ACCELEROMETER = 3;

	private Handler _handler;
	public Socket _socket;
	private InputStream _istream;
	private OutputStream _ostream;
	
	private byte[] _sendBuffer = new byte[BUFFER_SIZE];
	
	
	private TcpObject MakeTcpObj(int type) {
		switch (type) {
		case ENUM_COMMAND:
			return null;
		case ENUM_TEXT:
			return new TcpText();
		case ENUM_DUMMY:
			return new TcpDummy();
		}
		
		return null;
	}

	public TcpConnection(Socket s) throws IOException {
		this(s, new Handler());
	}

	public TcpConnection(Socket s, Handler handler) throws IOException {
		_handler = handler;
		_socket = s;
		s.setTcpNoDelay(true);
		_istream = _socket.getInputStream();
		_ostream = _socket.getOutputStream();
		
		Thread thread = new Thread(this);
        thread.start();
	}

	public void close() throws IOException {
		_socket.close();
	}
	
	public void send(TcpObject obj) throws IOException {
		int len = obj.GetStream(_sendBuffer, BUFFER_SIZE);
		_ostream.write(_sendBuffer, 0, len);
	}
	
	public void run() {
		
		try {
			byte[] _recvBuffer = new byte[BUFFER_SIZE];
			ByteBuffer _recvByteBuffer = ByteBuffer.wrap(_recvBuffer);
			_recvByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
			boolean _recieving = false;
			int _size = 0;
			int _recvSize = 0;
			int _packetCode = 0;
			int _checksum = 0;
			int l = 0;
			
			while(l != -1) {
				if (!_recieving) {
					// read the header
					_recvSize += l = _istream.read(_recvBuffer,
							_recvSize, 12 - _recvSize);

					if (_recvSize == 12) {	// read header complete
						_recieving = true;
						_size = _recvByteBuffer.getInt(0);
						_packetCode = _recvByteBuffer.getInt(4);
						_checksum = _recvByteBuffer.getInt(8);

						if ((_size ^ _packetCode) != _checksum) {
							throw new IOException();
						}
					}
				} else {
					// read the body
					_recvSize += l = _istream.read(_recvBuffer,
							_recvSize, _size - _recvSize);

					if (_recvSize == _size) {	// read body complete
						// Make packet
						TcpObject tcpObj = MakeTcpObj(_packetCode);
						tcpObj.FromStream(_recvBuffer);
						
						// Reset variable
						_recieving = false;
						_size = 0;
						_recvSize = 0;
						l = 0;
						
						// Deliver packet to handler
						_handler.sendMessage(Message.obtain(_handler, 
								TcpConnection.DID_RECEIVE, tcpObj));
					}
				}
			}
			
			_handler.sendMessage(Message
					.obtain(_handler, TcpConnection.DID_CLOSE));
			
		} catch (IOException e) {
			if (e.getMessage().equalsIgnoreCase("Socket closed")) {
				_handler.sendMessage(Message
						.obtain(_handler, TcpConnection.DID_CLOSE));
			} else {
				_handler.sendMessage(Message
						.obtain(_handler, TcpConnection.DID_ERROR));
			}
		}
	}
}